/* 
*  Copyright (C) 2009-2010 WCG Project
*
*  This program is free software; you can redistribute it and/or modify it
*  under the terms of the GNU General Public License as published by the
*  Free Software Foundation; either version 2 of the License, or (at
*  your option) any later version.
*
*  This program is distributed in the hope that it will be useful, but
*  WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
*  General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program; if not, write to the Free Software Foundation,
*  Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*  In addition, as a special exception, the author gives permission to
*  link the code of this program with the Half-Life Game Engine ("HL
*  Engine") and Modified Game Libraries ("MODs") developed by Valve,
*  L.L.C ("Valve"). You must obey the GNU General Public License in all
*  respects for all of the code used other than the HL Engine and MODs
*  from Valve. If you modify this file, you may extend this exception
*  to your version of the file, but you are not obligated to do so. If
*  you do not wish to do so, delete this exception statement from your
*  version.
*/



//-------------------------------------------
// WCG_Precache
//
//-------------------------------------------
public WCG_Precache()
{
	new i

	// Precache wc3.css (if it exists!)
	if( !file_exists( "wcg.css" ) )
	{
		WC3_Log( true, "[ERROR] Missing file 'wcg.css'" )
		set_fail_state( "A required file is missing, unable to load plugin" )
	}
	else
		precache_generic( "wcg.css" )

	copy( g_szSounds[SOUND_NULL],				63, "common/null.wav"								)
	copy( g_szSounds[SOUND_BANISH],				63, "wcg/banishcaster.wav"							)	// Banish
	copy( g_szSounds[SOUND_VOODOO],				63, "wcg/divineshield.wav"							)	// Big Bad Voodoo
	copy( g_szSounds[SOUND_LIGHTNING],			63, "wcg/lightningbolt.wav"							)	// Chain Lightning
	copy( g_szSounds[SOUND_PICKUPITEM],			63, "wcg/pickupitem.wav"							)	// Buy / Pick up item
	copy( g_szSounds[SOUND_TOME],				63, "wcg/Tomes.wav"									)	// Tome of Experience
	copy( g_szSounds[SOUND_ULTIMATESCAN],		63, "turret/tu_ping.wav"							)	// Ultimate Beep
	copy( g_szSounds[SOUND_ULTIMATEREADY],		63, "wcg/resurrecttarget.wav"						)	
	copy( g_szSounds[SOUND_IMMOLATE],			63, "wcg/ImmolationDecay1.wav"						)	// Immolate
	copy( g_szSounds[SOUND_IMMOLATE_BURNING],	63, "ambience/flameburst1.wav"				    	)	// Immolate Burning
	copy( g_szSounds[SOUND_REINCARNATION],		63, "wcg/reincarnation.wav"							)	// Weapon Reincarnation/Ankh
	copy( g_szSounds[SOUND_ANTEND],				63, "wcg/antend.wav"								)	// Sound played when you try to shoot a chameleoned teammate 
	copy( g_szSounds[SOUND_ERROR],				63, "wcg/Error.wav"									)	// Sound played on errors 
	copy( g_szSounds[SOUND_DISPELL],			63, "wcg/DispelMagicTarget.wav"						)	// Dispell an ultimate - played on caster
	copy( g_szSounds[SOUND_SPELLSHIELD],		63, "wcg/SpellShieldImpact1.wav"					)	// Dispell an ultimate - played on blocker
	copy( g_szSounds[SOUND_LEVELUP],			63, "wcg/LevelUp.wav"								)	// Level up
	copy( g_szSounds[SOUND_ENTANGLINGSTART],	63, "wcg/EntanglingRoots.wav"						)	// Entangling Roots
	copy( g_szSounds[SOUND_ENTANGLINGEND],		63, "wcg/EntanglingRootsEndState.wav"				)	// Entangling Roots ending
	copy( g_szSounds[SOUND_TRANQUILITY],		63, "wcg/Tranquility.wav"							)	// Tranquility
	copy( g_szSounds[SOUND_STRIKE],				63, "wcg/Strike.wav"								)
	copy( g_szSounds[SOUND_HEAL_LOW_BASE],		63, "wcg/Heal_Low_Base.wav"							)
	copy( g_szSounds[SOUND_PRECAST_HOLY],		63, "wcg/PreCastHolyMagicLow.wav"					)
	copy( g_szSounds[SOUND_CAST_HOLY],			63, "wcg/HolyCast.wav"								)
	copy( g_szSounds[SOUND_STEALTH],			63, "wcg/Stealth.wav"								)
	copy( g_szSounds[SOUND_TELEPORT],			63, "wcg/Teleport.wav"								)
	copy( g_szSounds[SOUND_POLYMORPHCHICKEN],	63, "wcg/PolyMorphChicken.wav"						)
	copy( g_szSounds[SOUND_KILLCHICKEN],		63, "wcg/killChicken.wav"							)
	copy( g_szSounds[SOUND_BLIZZARDIMPACT],		63, "wcg/BlizzardImpact1a.wav"						)
	copy( g_szSounds[SOUND_FROSTNOVA],			63, "wcg/FrostNova.wav"								)
	copy( g_szSounds[SOUND_FIRENOVA],			63, "wcg/FireNova.wav"								)
	copy( g_szSounds[SOUND_RESURRECT],			63, "wcg/Resurrection.wav"							)
	copy( g_szSounds[SOUND_SOULSTONE],			63, "wcg/SoulstoneResurrection.wav"					)
	copy( g_szSounds[SOUND_FLASH_HEAL],			63, "wcg/FlashHeal_Low_Base.wav"					)
	copy( g_szSounds[SOUND_HOLYBOLT],			63, "wcg/HolyBolt.wav"								)
	copy( g_szSounds[SOUND_FORTITUDE],			63, "wcg/BurningSpirit.wav"							)
	copy( g_szSounds[SOUND_WINGCLIP],			63, "wcg/WingClip.wav"								)
	copy( g_szSounds[SOUND_SERPENTSTING],		63, "wcg/BestowDiseaseImpact.wav"					)
	copy( g_szSounds[SOUND_ARCANEMISSILE],		63, "wcg/ArcaneMissileImpact1b.wav"					)
	copy( g_szSounds[SOUND_DECISIVESTRIKE],		63, "wcg/DecisiveStrike.wav"						)
	copy( g_szSounds[SOUND_RESISTANCEAURA],		63, "wcg/ResistanceAura.wav"						)
	copy( g_szSounds[SOUND_BLOODLUST],			63, "wcg/bloodlust.wav"								)
//	copy( g_szSounds[SOUND_AMBUSH],				63, "wcg/Ambush.wav"								)



	// Precache all sprites
	g_spr_nhth1			= precache_model( "sprites/nhth1.spr"				)
	g_spr_animglow01	= precache_model( "sprites/animglow01.spr"			)
	g_spr_explode1		= precache_model( "sprites/explode1.spr"			)
	g_spr_xfire			= precache_model( "sprites/xfire.spr"				)
	g_spr_stmbal1		= precache_model( "sprites/stmbal1.spr"				)
	g_spr_smoke			= precache_model( "sprites/smoke.spr"				)
	g_spr_shockwave		= precache_model( "sprites/shockwave.spr"			)
	g_spr_lgtning		= precache_model( "sprites/lgtning.spr"				)
	g_spr_blueflare2	= precache_model( "sprites/blueflare2.spr"			)
	g_spr_fireball		= precache_model( "sprites/wcg/fireball.spr"	)
	g_spr_zbeam1		= precache_model( "sprites/zbeam1.spr"				)
	g_spr_xspark2		= precache_model( "sprites/xspark2.spr"				)

//	g_spr_test			= precache_model( "sprites/nhth1.spr"				)

	// Precache all sounds
	for ( i = 0; i < MAX_SOUNDS ; i++ )
		precache_sound( g_szSounds[i] )

	// Precache models
	if( GameInfo[GI_Mod] == GAME_CZERO )
	{
		precache_model( "models/player/spetsnaz/spetsnaz.mdl" )
		precache_model( "models/player/militia/militia.mdl" )
	}

	precache_model( "models/player/gign/gign.mdl" )
	precache_model( "models/player/sas/sas.mdl" )
	precache_model( "models/player/gsg9/gsg9.mdl" )
	precache_model( "models/player/urban/urban.mdl" )
	precache_model( "models/player/arctic/arctic.mdl" )
	precache_model( "models/player/leet/leet.mdl" )
	precache_model( "models/player/guerilla/guerilla.mdl" )
	precache_model( "models/player/terror/terror.mdl" )

	precache_model( "models/wcg/chicken.mdl" )
	precache_model( "models/wcg/frostnova.mdl" )
}


//-------------------------------------------
// WC3_Init
//
// Function called when the plugin loads
//-------------------------------------------
public WC3_Init()
{
	// Store this now so we don't need to make a native call everytime we use it
	GameInfo[GI_MaxPlayers] = global_get( glb_maxClients )
	
	// Register the CVARs
	CVAR_Init()
	
	// Initialize shared functions
	SHARED_Init()

	// Initialize our items
	ITEM_Init()
	
	// Initialize our skills
	SM_Init()

	// Register the player menus
	LANG_SetMenus()

	// Register the events we want to catch
	Register_Events()

	// Register any forwards
	Register_Forwards()

	// We need to execute config file in a bit don't we?
	set_task( 0.3, "_CVAR_ExecuteConfig", TASK_EXECUTECONFIG )

	// Configure the database connection
	set_task( 1.0, "DB_Init", TASK_SETSQL )

	// Set up things when our config file has loaded the values
	set_task( 1.5, "_WCG_RunAfterConfig", TASK_AFTERCONFIG )

	// cl_minmodels check
//	set_task( 4.5, "_CS_MinModelsLoop" )

	// Run the heartbeat task and keep it running every second
	set_task( 1.0, "_WCG_Global_Heartbeat", TASK_GLOBAL_HEARTBEAT, _, _, "b" )

	// Lets find out if we should disable orc nades or gloves of warmth
	GameInfo[GI_DisableEnhGrenades] = WC3_MapDisableCheck( "skill_orc_nade.cfg" )
	GameInfo[GI_GlovesDisabled] = WC3_MapDisableCheck( "item_gloves.cfg" )

	static szCurMap[32]
	get_mapname( szCurMap, 31 )
	WC3_Log( false, "** Map %s started, version: '%s'", szCurMap, PLUGIN_VERSION )
}




//-------------------------------------------
// _WCG_RunAfterConfig
//
//-------------------------------------------
public _WCG_RunAfterConfig()
{
	// Configure the XP based on level
	XP_Configure()

	// Set up our CVARs - some of them
	CVAR_Configure()
}



//-------------------------------------------
// WC3_GetUserInput
//
// Function will prompt a user for a race or skill selection
//-------------------------------------------
public WC3_GetUserInput( id )
{
	id = GetTaskID( id, TASK_GETINPUT )

	if ( !is_user_connected(id) )
		return

	// User has no race
	if( PlayerInfo[id][PI_Class] == CLASS_NONE )
	{
		new menu_id, keys
		new menuUp = player_menu_info( id, menu_id, keys )

		// Only display menu if another isn't shown
		if ( menuUp <= 0 || menu_id < 0 )
			WCG_ChangeClassStart( id )

		// Try again in 1 second
		else
			set_task( 1.0, "WC3_GetUserInput", TASK_GETINPUT + id )
	}

	// User has skills points available
	else if( SM_SkillsAvailable( id ) )
	{
		if( is_user_alive(id) ) 
			MENU_SelectSkill( id )
	}
	else if( GetAvailableAttributePoints( id ) )
	{
		if( is_user_alive(id) ) 
			MENU_SelectAttribute( id )
	}
	else
		WC3_ShowBar( id )
}



//-------------------------------------------
// WC3_ResetGame
//
// Function will "reset" the game - i.e. "sv_restart 1"
//-------------------------------------------
public WC3_ResetGame()
{
	for( new id = 1 ; id <= GameInfo[GI_MaxPlayers] ; id++ )
	{
		if( !is_user_connected(id) )
			continue

		remove_task( TASK_FORCERACESELECT + id )

		// User didn't die last round... 
		PlayerInfo[id][PI_DiedLastRound] = false

		// Remove player's items
		ITEM_Remove( id, ITEM_SLOT_ONE )
		ITEM_Remove( id, ITEM_SLOT_TWO )

		// Reset item info
		g_bPlayerBoughtAnkh[id]		= false

		PlayerInfo[id][PI_HECount]		= 0
		PlayerInfo[id][PI_FlashCount]	= 0

		remove_task( TASK_EFFECT + id )

		WC3_ShowBar( id )
	}

	GameInfo[GI_GameRestarting] = false
}



//-------------------------------------------
// WCG_ChangeClassStart
//
// Function will grab XP for the user
//-------------------------------------------
WCG_ChangeClassStart( id )
{
	// Make sure the user is on a team!
	if( !IsOnTeam(id) )
	{
		client_print( id, print_center, "Please join a team before selecting a class!" )
		return
	}

	DB_GetAllXP( id )				// This function will also display the changerace menu
}



//-------------------------------------------
// WCG_ChangeClassShowMenu
//
// Function will show the "select a race" menu to the user
//-------------------------------------------
WCG_ChangeClassShowMenu( id, iRaceXP[MAX_CLASSES] = {0} )
{
	// We don't want to replace the player's current XP with whats in the database now do we ?
	if( PlayerInfo[id][PI_Class] )
		iRaceXP[PlayerInfo[id][PI_Class]-1] = PlayerInfo[id][PI_XP]

	// Need to call this here
	if( !PlayerInfo[id][PI_ChangingTeam] )
		MENU_ChangeClass( id, iRaceXP )
}



//-------------------------------------------
// WC3_SetRace
//
// This will actually give the user a given race
//-------------------------------------------
WC3_SetRace( id, iClass )
{
	// Set the user's race
	PlayerInfo[id][PI_Class] = iClass

	// Reset all race data
	SM_ResetSkillLevels( id )
	ResetAttributeLevels( id )

	PlayerInfo[id][PI_ChangeToRace] = CLASS_NONE

	SM_UpdateClassSkills( id, PlayerInfo[id][PI_Class] )

	PlayerInfo[id][PI_Level] = 0
	DB_SetDataForRace( id )
}



//-------------------------------------------
// WC3_ShowBar
//
//-------------------------------------------
WC3_ShowBar( id )
{
	if( !is_user_connected(id) || !IsOnTeam(id) )
		return

	new pos = 0
	static szXPInfo[64], szItemInfo[64], szRaceInfo[64]
	static szRaceName[64], szItemName[32]

	szXPInfo[0] = 0
	szItemInfo[0] = 0

	// Get the class names
	LANG_GetClassName( PlayerInfo[id][PI_Class], id, szRaceName, 63 )
	
	if( PlayerInfo[id][PI_Class] == CLASS_NONE )
		formatex( szRaceInfo, 63, "%s ", szRaceName )

	else
	{
		// User is under level MAX_LEVELS
		if ( PlayerInfo[id][PI_Level] < MAX_LEVELS )
		{
			formatex( szRaceInfo, 63, "[%d]  XP: %d/%d ", PlayerInfo[id][PI_Level], PlayerInfo[id][PI_XP], XP_GetByLevel( PlayerInfo[id][PI_Level]+1) )
			formatex( szXPInfo, 63, "%s  %L: %d   XP: %d/%d", szRaceName, id, "WORD_LEVEL", PlayerInfo[id][PI_Level], PlayerInfo[id][PI_XP], XP_GetByLevel( PlayerInfo[id][PI_Level]+1 ) )
		}			
			
		// User is level MAX_LEVELS
		else
		{
			formatex( szRaceInfo, 63, "[%d]  XP: %d ", PlayerInfo[id][PI_Level], PlayerInfo[id][PI_XP] )
			formatex( szXPInfo, 63, "%s  %L: %d   XP: %d", szRaceName, id, "WORD_LEVEL", PlayerInfo[id][PI_Level], PlayerInfo[id][PI_XP] )
		}
	}

	pos = 0

	// User has one item
	if( g_iShopMenuItems[id][ITEM_SLOT_ONE] != ITEM_NONE )
	{
		ITEM_Format( id, g_iShopMenuItems[id][ITEM_SLOT_ONE], szItemName, 31 )
		pos += formatex( szItemInfo[pos], 63-pos, "%s", szItemName )
	}

	// User has another item
	if( g_iShopMenuItems[id][ITEM_SLOT_TWO] != ITEM_NONE )
	{
		ITEM_Format( id, g_iShopMenuItems[id][ITEM_SLOT_TWO], szItemName, 31 )

		// Then the string isn't empty and we have information in it (so we have a first item)
		if ( szItemInfo[0] )
			pos += formatex( szItemInfo[pos], 63-pos, " %L %s", id, "WORD_AND", szItemName )

		// We don't need the word "and"
		else
			pos += formatex( szItemInfo[pos], 63-pos, "%s", szItemName )
	}

	formatex( PlayerInfo[id][PI_StartusBarRaceInfoText], 63, "%s", szRaceInfo )
	formatex( PlayerInfo[id][PI_StartusBarItemInfoText], 63, "%s", szItemInfo )

	if( is_user_alive(id) )
		WCG_UpdateStatusBar( id )

	else if( PlayerInfo[id][PI_Class] != CLASS_NONE )
	{
		set_hudmessage( 160, 160, 160, 0.012, 0.90, HUDMESSAGE_FX_FADEIN, 10.0, 0.0, 2.0, 3.0, HUD_XP )
		show_hudmessage( id, "%s", szXPInfo )
	}
}



//-------------------------------------------
// WCG_UpdateStatusBar
//
// Updates the player's statusbar text
//-------------------------------------------
WCG_UpdateStatusBar( id )
{
	if( !is_user_alive(id) || !IsOnTeam(id) )
		return

	if( PlayerInfo[id][PI_Class] == CLASS_NONE )
		formatex( szTmpMsg, 63, "%s", PlayerInfo[id][PI_StartusBarRaceInfoText] )

	else
		formatex( szTmpMsg, 127, "%s  Mana: %d  %s", PlayerInfo[id][PI_StartusBarRaceInfoText], \
				PlayerInfo[id][PI_Mana], PlayerInfo[id][PI_StartusBarItemInfoText] )

	Create_StatusText( id, 0, szTmpMsg )
}


//-------------------------------------------
// WCG_SortTeams
//
//-------------------------------------------
WCG_SortTeams( ExcludeID=0 )
{
	g_NumTeamPlayers[TEAM_UNASSIGNED] = 0
	g_NumTeamPlayers[TEAM_T] = 0
	g_NumTeamPlayers[TEAM_CT] = 0
	g_NumTeamPlayers[TEAM_SPECTATOR] = 0

	new iTeamID
	
	for( new id = 1 ; id <= GameInfo[GI_MaxPlayers] ; id++ )
	{
		if( !is_user_connected(id) || id == ExcludeID )
			continue

		iTeamID = fm_get_user_team(id)

		if( iTeamID != TEAM_UNASSIGNED && iTeamID != TEAM_SPECTATOR && iTeamID != TEAM_T && iTeamID != TEAM_CT )
			iTeamID = TEAM_UNASSIGNED

		PlayerInfo[id][PI_TeamID] = iTeamID

		g_SortedTeams[iTeamID][g_NumTeamPlayers[iTeamID]++] = id
	}
}


//-------------------------------------------
// WCG_ValidateTeam
//
// Checks if the player is cached in the correct team
//-------------------------------------------
WCG_ValidateTeam( id )
{
	new iTeamID = fm_get_user_team(id)

	for( new i = 0 ; i < g_NumTeamPlayers[iTeamID] ; i++ )
	{
		if( id == g_SortedTeams[iTeamID][i] )
			return true
	}
	return false
}


//-------------------------------------------
// WCG_ShowClassInfo
//
// Function will display the level information in the center of the user's screen
//-------------------------------------------
WCG_ShowClassInfo( id )
{
	if ( PlayerInfo[id][PI_Class] != CLASS_NONE )
	{
		static szSkillName[64], szRaceName[64], iSkillLevel, iSkillType
		static szTrainable[256], szUltimate[256], szAbility[256]
		
		new posT = 0, posU = 0, posA = 0

		szTrainable[0] = 0
		szAbility[0] = 0
		szUltimate[0] = 0

		// Get the class name
		LANG_GetClassName( PlayerInfo[id][PI_Class], id, szRaceName, 63 )

		// Add the header
		formatex( szTmpMsg, 1023, "%s^n%L %d", szRaceName, id, "WORD_LEVEL", PlayerInfo[id][PI_Level] )


		// Loop through all available skills to find the trainable options...
		for ( new iSkillID = 0; iSkillID < MAX_SKILLS; iSkillID++ )
		{
			// Get the skill's level
			iSkillLevel = SM_GetSkillLevel( id, iSkillID, 8 )
			iSkillType = SM_GetSkillType( iSkillID )

			// If the skill level is > 0 then the user has this skill!
			if( iSkillLevel > 0 )
			{
				// Get the skill's name
				LANG_GetSkillName( iSkillID , id, szSkillName, 63, 19 )

				// Trainable skill
				if( iSkillType == SKILL_TYPE_TRAINABLE )
					posT += formatex( szTrainable[posT], 255-posT, "^n%s %L %d", szSkillName, id, "WORD_LEVEL", iSkillLevel )

				// Skill is an ability
				else if( iSkillType == SKILL_TYPE_ABILITY )
					posA += formatex( szAbility[posA], 255-posA, "^nAbility: %s %L %d", szSkillName, id, "WORD_LEVEL", iSkillLevel )

				// Skill is an ultimate
				else if( iSkillType == SKILL_TYPE_ULTIMATE )
					posU += formatex( szUltimate[posU], 255-posU, "^n%L: %s %L %d", id, "WORD_ULTIMATE", szSkillName, id, "WORD_LEVEL", iSkillLevel )
			}
		}


		// Add all the skills to the message!
		strcat( szTmpMsg, szTrainable, 255 )
		strcat( szTmpMsg, szAbility, 255 )
		strcat( szTmpMsg, szUltimate, 255 )

		WCG_StatusText( id, TXT_RACE_INFO, HUD_RACE_INFO, szTmpMsg )
	}
	
	// User has no race
	else
		WCG_StatusText( id, TXT_RACE_INFO, HUD_RACE_INFO, "You need to select a race first!" )
}




//-------------------------------------------
// WC3_ResetSkills
//
//-------------------------------------------
WC3_ResetSkills( id )
{
	// Reset the user's skills
	if ( PlayerInfo[id][PI_ResetSkills] ) 
	{
//		PlayerInfo[id][PI_Level] = 0
		SM_ResetSkillLevels( id )

		WC3_ShowBar( id )
		XP_Check( id, false )

		PlayerInfo[id][PI_ResetSkills] = false

		return true
	}

	return false
}

//-------------------------------------------
// WC3_ResetAttributes
//
//-------------------------------------------
WC3_ResetAttributes( id )
{
	// Reset the user's attributes
	if ( PlayerInfo[id][PI_ResetAttributes] ) 
	{
		ResetAttributeLevels( id, 3 )

		WC3_ShowBar( id )
		XP_Check( id, false )

		PlayerInfo[id][PI_ResetAttributes] = false

		return true
	}

	return false
}



//-------------------------------------------
// WC3_ShowSpecInfo
//
//-------------------------------------------
WCG_ShowSpecInfo( id, iTargetID )
{
	// Get the target's name
	new szTargetName[32]
	get_user_name( iTargetID, szTargetName, 31 ) 
	
	// Get the target's class name
	new szRaceName[64]
	LANG_GetClassName( PlayerInfo[iTargetID][PI_Class], id, szRaceName, 63 )
	
	new szMsg[512], iMsgPos = 0

	// User is level 0
	if ( PlayerInfo[iTargetID][PI_Level] == 0 )
	{
		iMsgPos += formatex( szMsg, 511, "%s  XP: %d/%d", szRaceName, PlayerInfo[iTargetID][PI_XP], XP_GetByLevel( PlayerInfo[iTargetID][PI_Level]+1 ) )
	}

	// User is under level MAX_LEVELS
	else if ( PlayerInfo[iTargetID][PI_Level] < MAX_LEVELS )
	{
		iMsgPos += formatex( szMsg, 511, "%s %L: %d   XP: %d/%d", szRaceName, id, "WORD_LEVEL", PlayerInfo[iTargetID][PI_Level], PlayerInfo[iTargetID][PI_XP], XP_GetByLevel( PlayerInfo[iTargetID][PI_Level]+1 ) )
	}			
		
	// User is level MAX_LEVELS
	else
	{
		iMsgPos += formatex( szMsg, 511, "%s %L: %d   XP: %d", szRaceName, id, "WORD_LEVEL", PlayerInfo[iTargetID][PI_Level], PlayerInfo[iTargetID][PI_XP] )
	}
	
	new szItemName[32], szItemName2[32]
	new bool:ItemSlotOne = false

	// Get the item and race names
	LANG_GetItemName( g_iShopMenuItems[iTargetID][ITEM_SLOT_ONE], id, szItemName, 31, true )
	LANG_GetItemName( g_iShopMenuItems[iTargetID][ITEM_SLOT_TWO], id, szItemName2, 31, true )

	// User has one item
	if( g_iShopMenuItems[iTargetID][ITEM_SLOT_ONE] > ITEM_NONE )
	{
		// Then they have rings, lets print how many there are
		if( g_iShopMenuItems[iTargetID][ITEM_SLOT_ONE] == ITEM_RING && PlayerInfo[iTargetID][PI_Rings] > 1 )
			iMsgPos += formatex( szMsg[iMsgPos], 512-iMsgPos, "^nItem: %s x%d", szItemName, PlayerInfo[iTargetID][PI_Rings] )
		else
			iMsgPos += formatex( szMsg[iMsgPos], 512-iMsgPos, "^nItem: %s", szItemName )
		
		ItemSlotOne = true
	}


	// User has another item
	if( g_iShopMenuItems[iTargetID][ITEM_SLOT_TWO] > ITEM_NONE )
	{
		// Then the string isn't empty and we have information in it (so we have a first item)
		if( ItemSlotOne )
		{
			// Then they have rings, lets print how many there are
            if( g_iShopMenuItems[iTargetID][ITEM_SLOT_TWO] == ITEM_RING && PlayerInfo[iTargetID][PI_Rings] > 1 )
                iMsgPos += formatex( szMsg[iMsgPos], 512-iMsgPos, " %L %s x%d", id, "WORD_AND", szItemName2, PlayerInfo[iTargetID][PI_Rings] );
            else
                iMsgPos += formatex( szMsg[iMsgPos], 512-iMsgPos, " %L %s", id, "WORD_AND", szItemName2 );
		}
		else // We don't need the word "and"
		{
			// Then they have rings, lets print how many there are
            if( g_iShopMenuItems[iTargetID][ITEM_SLOT_TWO] == ITEM_RING && PlayerInfo[iTargetID][PI_Rings] > 1 )
                iMsgPos += formatex( szMsg[iMsgPos], 512-iMsgPos, "^nItem: %s x%d", szItemName2, PlayerInfo[iTargetID][PI_Rings] );
            else
                iMsgPos += formatex( szMsg[iMsgPos], 512-iMsgPos, "^nItem: %s", szItemName2 );
		}
	}

	// Add the Health + Armor to the message
	iMsgPos += formatex( szMsg[iMsgPos], 512-iMsgPos, "^n%L: %d^n%L: %d", id, "CURRENT_HEALTH", get_user_health( iTargetID ), id, "WORD_ARMOR", get_user_armor( iTargetID ) )
	
	// Format the message
	new Float:fSpecTime = SPEC_HOLDTIME

	if ( get_pcvar_num( CVAR_spec_position ) == 0 )
		set_hudmessage( 255, 255, 255, 0.018, 0.9, 2, 1.5, fSpecTime, 0.02, 5.0, HUD_SPEC_INFO )
	else
		set_hudmessage( 255, 255, 255, 0.65, 0.9, 2, 1.5, fSpecTime, 0.02, 5.0, HUD_SPEC_INFO )
	
	// Show the message
	show_hudmessage( id, szMsg )
}



//-------------------------------------------
// WC3_StatusText
//
// Function will print a message in the center of the screen
//-------------------------------------------
WCG_StatusText( id, iType, iChannel, const fmt[], ... )
{
	static szFormattedText[512]

	vformat( szFormattedText, 511, fmt, 5 )

	switch( iType )
	{
		case TXT_TOP_CENTER:
			set_hudmessage( 200, 100, 0, -1.0, 0.25, HUDMESSAGE_FX_FADEIN, 1.0, 2.0, 0.1, 0.2, iChannel )

		case TXT_RACE_INFO:
			set_hudmessage( 255, 255, 255, -1.0, 0.3, HUDMESSAGE_FX_FADEIN, 3.0, 5.0, 0.1, 0.2, iChannel )

		case TXT_ULTIMATE:
			set_hudmessage( 255, 208, 0, -1.0, 0.85, HUDMESSAGE_FX_FADEIN, 6.0, 0.5, 0.1, 0.5, iChannel )

		case TXT_BLINK_CENTER:
			set_hudmessage( 178, 14, 41, -1.0, -0.4, HUDMESSAGE_FX_FLICKER, 0.5, 1.7, 0.2, 0.2, iChannel )

		case TXT_SKILL:
			set_hudmessage( 0, 0, 255, 0.75, 0.85, HUDMESSAGE_FX_FADEIN, 6.0, 3.0, 0.2, 0.7, iChannel )

		case TXT_TIMER:
			set_hudmessage( 0, 100, 0, 0.05, 0.65, HUDMESSAGE_FX_WRITEOUT, 0.02, 10.0, 0.01, 0.1, iChannel )

		case TXT_CASTING:
			set_hudmessage( 255, 255, 10, -1.0, -0.4, HUDMESSAGE_FX_FLICKER, 0.5, 6.0, 0.2, 0.2, iChannel )

		default:
			set_hudmessage( 255, 255, 10, -1.0, -0.4, HUDMESSAGE_FX_FLICKER, 0.5, 2.0, 0.2, 0.2, iChannel )
	}

	show_hudmessage( id, szFormattedText )
}


//-------------------------------------------
// WC3_Kill
//
//-------------------------------------------
stock WC3_Kill( iVictim, iKiller, iWeapon, bShouldGib = 0 )
{
	if( !is_user_alive(iVictim) )
		return

	PlayerInfo[iVictim][PI_LastHitByWeaponIndex] = iWeapon

	set_msg_block( gmsgDeathMsg, BLOCK_ONCE )

	ExecuteHamB( Ham_Killed, iVictim, iKiller, bShouldGib )

	UTIL_GetWeaponName( iWeapon, szTmpMsg, 31 )

	Create_DeathMsg_CS( iKiller, iVictim, false, szTmpMsg )
}



//-------------------------------------------
// WC3_MapDisableCheck
//
// Funtion will check a file to see if the mapname exists
//-------------------------------------------
bool:WC3_MapDisableCheck( szFileName[] )
{
	// Format the Orc Nade Disable File
	new szFile[128]
	get_configsdir( szFile, 127 )
	formatex( szFile, 127, "%s/wcg/disable/%s", szFile, szFileName )

	if ( !file_exists( szFile ) )
		return false

	new iLineNum, szData[64], iTextLen, iLen
	new szMapName[64], szRestrictName[64]
	get_mapname( szMapName, 63 )

	while ( read_file( szFile, iLineNum, szData, 63, iTextLen ) )
	{
		iLen = copyc( szRestrictName, 63, szData, '*' )

		if ( equali( szMapName, szRestrictName, iLen ) )
		{
			return true
		}

		iLineNum++
	}

	return false
}



//-------------------------------------------
// WC3_NewRound
//
// Function called at the start of a new round on each user id, before they have spawned
//-------------------------------------------
WC3_NewRound( id )
{
	// Remove the spectating info if necessary
	if ( g_fLastSpecDisplay[id] >= get_gametime() )
		UTIL_ClearHudChannel( id, HUD_SPEC_INFO )

	// Reset who the user was spectating
	PlayerInfo[id][PI_SpectatingID] = -1

	// Reset the player's role
	PlayerInfo[id][PI_PlayerRole] = 0
	
	// Reset the bomb/defusing check
	PlayerInfo[id][PI_HasBegunPlantingOrDefusing] = false
	PlayerInfo[id][PI_IsSearching] = false

	// Reset all ultimates
	remove_task( TASK_FUNNELS + id )

	// Reset certain player variables
	PlayerInfo[id][PI_HasSpawned] = false
	PlayerInfo[id][PI_RespawnBy] = 0

	// Save a copy of what weapons the user had the previous round (for weapon reincarnation)
	SHARED_CopySavedWeapons( id )

	remove_task( TASK_EFFECT + id )

	// Remove any reset_maxspeeds occuring (could cause a person to move during freezetime)
	remove_task( TASK_RESETSPEED + id )

	// Remove any tranquility effects
	remove_task( TASK_TRANQUILITY + id )
}





//-------------------------------------------
// WC3_PlayerSpawn_NewRound
//
// This is called only at the start of a new round, after a user has spawned, before WC3_PostSpawn()
//-------------------------------------------
WC3_PlayerSpawn_NewRound( id )
{
	// If it's a bot, should we change the race?
	if ( is_user_bot( id ) )
	{
		if( !PlayerInfo[id][PI_Class] )
		{
			PlayerInfo[id][PI_Class] = random_num( 1, MAX_CLASSES )
			WC3_SetRace( id, PlayerInfo[id][PI_Class] )
		}
	}

	// Reset Assist XP handler!
	for( new i = 1; i <= GameInfo[GI_MaxPlayers]; i++ )
	{
		g_iDamageDealt[id][i] = 0
		g_iHealingDone[id][i] = 0
	}

	// User has a race selection pending, set it
	//	- Changing race should get priority over reseting skills!
	if( PlayerInfo[id][PI_ChangeToRace] )
	{
		WC3_SetRace( id, PlayerInfo[id][PI_ChangeToRace] )
		
		if( PlayerInfo[id][PI_ResetSkills] )
		{
			client_print( id, print_chat, "%s Since you changed your race, your skills will no longer be reset.  Type /resetskills to re-enable", g_MODclient )
			PlayerInfo[id][PI_ResetSkills] = false
		}
		if( PlayerInfo[id][PI_ResetAttributes] )
		{
			client_print( id, print_chat, "%s Since you changed your race, your attributes will no longer be reset.  Type /resetattribs to re-enable", g_MODclient )
			PlayerInfo[id][PI_ResetAttributes] = false
		}
	}

	// Reset user skills if we need to
//	if( WC3_ResetSkills( id ) )
//		return									// We need to return here b/c we don't want to set everyones' abilities!

	// Reset user skills if we need to
//	if( WC3_ResetAttributes( id ) )
//		return

	WC3_ResetSkills( id )
	WC3_ResetAttributes( id )

	InitAttributeEffects( id )

	// Find out if they need to choose a race or select a skill
	set_task( 0.3, "WC3_GetUserInput", TASK_GETINPUT + id )
}



//-------------------------------------------
// WC3_PostSpawn
//
// This is called at the end everytime a user spawns
//-------------------------------------------
WC3_PostSpawn( id )
{
	// Make sure the spawned player is cashed into the correct team
	if( !WCG_ValidateTeam( id ) )
		WCG_SortTeams()

	remove_task( TASK_SEARCHPING + id )
	remove_task( TASK_ALIVE_EFFECT + id )
	remove_task( TASK_STEALTH + id )

	_RemovePolyChicken( id )
	_RemoveFrostNova( id ) 

	if( PlayerInfo[id][PI_RespawnBy] == RESPAWN_RESURRECT )
		emit_sound( id, CHAN_STATIC, g_szSounds[SOUND_RESURRECT], VOL_NORM, ATTN_NORM, 0, PITCH_NORM )

	else if( PlayerInfo[id][PI_RespawnBy] == RESPAWN_SOULSTONE )
		emit_sound( id, CHAN_STATIC, g_szSounds[SOUND_SOULSTONE], VOL_NORM, ATTN_NORM, 0, PITCH_NORM )

	// Set the users mana
	PlayerInfo[id][PI_fMana]				= _:STARTING_MANA
	PlayerInfo[id][PI_Mana]					= clamp( floatround(PlayerInfo[id][PI_fMana]), 0, PlayerInfo[id][PI_MaxMana] )

	PlayerInfo[id][PI_ChangingTeam]			= false				// User isn't changing a team if they just spawned
	PlayerInfo[id][PI_IsZoomed]				= false				// User won't be zoomed when they spawn!
	PlayerInfo[id][PI_HECount]				= 0
	PlayerInfo[id][PI_CanRender]	 		= true
	PlayerInfo[id][PI_IsBurning]			= false				// User should not be burning
	PlayerInfo[id][PI_IsCheatDeathActive]	= false
	PlayerInfo[id][PI_Stealthed]			= 0
	PlayerInfo[id][PI_IsPolymorphed] 		= false

	PlayerInfo[id][PI_SerpentCount]			= 0


	PLAYER_SetDefaults( id )

	SM_SetSpawnCooldowns( id )									// Set cooldowns for all the spells
	PLAYER_RemoveSpellCasting( id )								// User isn't casting any spell
	SHARED_ResetMaxSpeed( id )									// The user should not be frozen when they spawn
	CS_ChangeSkin( id, SKIN_RESET )								// Reset the user's skin
	ITEM_GiveAllBonuses( id )									// Give the user their item bonuses!
	PLAYER_CheckEqReincarnation( id )							// If we need to give the user their weapons back, then lets

	// Check for bomb on the back of chameleon
	if( PlayerInfo[id][PI_SkinSwitched] )
		set_pev( id, pev_body, 0 )

	// Set this last - otherwise some skills might be screwed up
	PlayerInfo[id][PI_DiedLastRound] = false

	if( PlayerInfo[id][PI_Class] == CLASS_NONE && !task_exists( TASK_FORCERACESELECT + id ) )
	{
		new param[2]
		param[0] = id
		param[1] = 30
		set_task( 1.0, "_WCG_ForceClassSelect", TASK_FORCERACESELECT + id, param, 2 )
	}

	set_task( 1.0, "WC3_GetUserInput", TASK_GETINPUT + id )
}


//-------------------------------------------
// _WCG_ForceClassSelect
//
// Task to assign a race to a player after 30 seconds if none has been choosen
//-------------------------------------------
public _WCG_ForceClassSelect( param[] )
{
	new id 		= param[0]
	new counter	= param[1]
	new raceid
	
	if( !is_user_connected(id) || PlayerInfo[id][PI_Class] != CLASS_NONE || !IsOnTeam(id) )
		return

	if( counter <= 0 )
	{
		raceid = random_num( 1, MAX_CLASSES )
		
		WC3_SetRace( id, raceid )
		emit_sound( id, CHAN_STATIC, g_szSounds[SOUND_LEVELUP], VOL_NORM, ATTN_NORM, 0, PITCH_NORM )

		new menu_id, keys
		new menuUp = player_menu_info( id, menu_id, keys )

		if( menuUp > 0 )
		{
			// Hide race selection menu
			static szMenu[31]
			formatex( szMenu, 31, "%L", id, "MENU_SELECT_CLASS" )
			show_menu( id, 0, szMenu, -1 )
		}
		return
	}

	else if( counter == 30 || counter == 15 ||counter == 10 || counter <= 5 )
		client_print( id, print_chat, "%s You have %d seconds to select a race or one will be selected automatically.", g_MODclient, counter )

	param[1] = --counter
	set_task( 1.0, "_WCG_ForceClassSelect", TASK_FORCERACESELECT + id, param, 2 )
}



//-------------------------------------------
// WCG_PlayerInit
//
// Called when a player first joins the server! - we need to reset everything!
//-------------------------------------------
WCG_ResetPlayerInfo( id )
{
	// Clear the entire PlayerInfo array
	for( new i = 0 ; i < _:S_PLAYERINFO ; i++ )
		PlayerInfo[id][S_PLAYERINFO:i] = 0

	// Reset all skill information for this player!
	SM_ResetSkillLevels( id )
	ResetAttributeLevels( id, 4 )

	// This player shouldn't have a race/level or xp yet! - I mean they did just join duh!
	PlayerInfo[id][PI_Class]				= CLASS_NONE
	PlayerInfo[id][PI_ChangeToRace]			= CLASS_NONE			// User shouldn't have a changerace pending
	PlayerInfo[id][PI_TeamID]				= TEAM_UNASSIGNED	// Player doesn't have a team yet! So reset it!
	PlayerInfo[id][PI_SelectedUltimate]		= SKILL_NONE
	PlayerInfo[id][PI_SelectedAbility]		= SKILL_NONE
	PlayerInfo[id][PI_CanRender]			= true

	g_bLevitation[id]						= true				// By default a user's levitation should be enabled!
	g_bPlayerBoughtAnkh[id]					= false				// User didn't buy an ankh!

	// User should have no items on connect...
	g_iShopMenuItems[id][0] 				= ITEM_NONE
	g_iShopMenuItems[id][1] 				= ITEM_NONE

	bIgnoreArmorSet[id]						= false

	g_iDBPlayerUniqueID[id]					= 0					// Reset the user's UniqueID

	for( new i = 1; i <= GameInfo[GI_MaxPlayers]; i++ )
	{
		g_iDamageDealt[id][i] = 0
		g_iHealingDone[id][i] = 0
	}

	bDBXPRetrieved[id]						= false				// User hasn't gotten his/her XP since he's being reset right?

	// Reset charges!
	g_iTotalRings[id]						= 0
	g_iHelmCharges[id]						= 0

	ULT_ClearIcons( id )
	Ability_ClearIcons( id )
}


//-------------------------------------------
// _WCG_Global_Heartbeat
//
// Task ran every second
//-------------------------------------------
public _WCG_Global_Heartbeat()
{
	static lastManaUpdateValue[33], bToggle, iHealthCounter, Float:fHealthFraction[33]

	new id, Float:fHealth, Float:fMaxHealth

	bToggle ^= 1
	GameInfo[GI_Heartbeat]++

	// Health regen
	if( !GameInfo[GI_FreezeTime] )
	{
		iHealthCounter = (iHealthCounter + 1) % 4
	
		for( id = iHealthCounter*8+1 ; id <= iHealthCounter*8+8 && id <= GameInfo[GI_MaxPlayers] ; id++ )
		{
			if( !is_user_connected(id) || !is_user_alive(id) || !IsOnTeam(id) )
				continue
	
			pev( id, pev_health, fHealth )
			fMaxHealth = float(PLAYER_GetMaxhealth( id ))
			
			if( fHealth < fMaxHealth )
			{
				fHealth += (float(PlayerInfo[id][PI_Level]+PlayerInfo[id][PI_SPR]*6)*0.0857143) + fHealthFraction[id]
				fHealth = floatmin( fHealth, fMaxHealth )
				fHealthFraction[id] = floatfract( fHealth )
				fHealth = float( floatround( fHealth, floatround_floor ) )

				// Storing decimal values in pev_health can cause weird behaviour
				set_pev( id, pev_health, fHealth )
			}
		}
	}


	for( id = 1 ; id <= GameInfo[GI_MaxPlayers] ; id++ )
	{
		if( !is_user_connected(id) || !IsOnTeam(id) )
			continue

		if( bToggle && PlayerInfo[id][PI_Class] == CLASS_NONE )
		{
			if( !task_exists( TASK_GETINPUT + id ) )
				WC3_GetUserInput( id )
		}

		if( GameInfo[GI_FreezeTime] || !is_user_alive(id) )
			continue

		// Mana regen
		if( PlayerInfo[id][PI_Mana] < PlayerInfo[id][PI_MaxMana] || lastManaUpdateValue[id] < PlayerInfo[id][PI_MaxMana] )
		{
			// Regen the players mana
			PlayerInfo[id][PI_fMana] += 1.0 + (float(PlayerInfo[id][PI_Level]/4+PlayerInfo[id][PI_SPR]*6)*0.03479)
			PlayerInfo[id][PI_fMana] = _:floatclamp( PlayerInfo[id][PI_fMana], 0.0, float(PlayerInfo[id][PI_MaxMana]) )
			PlayerInfo[id][PI_Mana] = clamp( floatround(PlayerInfo[id][PI_fMana]), 0, PlayerInfo[id][PI_MaxMana] )

			// Update the statusbar every other second if needed
			if( bToggle && !is_user_bot( id ) )
			{
				lastManaUpdateValue[id] = PlayerInfo[id][PI_Mana]
				WCG_UpdateStatusBar( id )
			}
		}

		SM_UpdateCooldowns( id )
		IconHandler( id )
		
		switch( PlayerInfo[id][PI_Class] )
		{
			case CLASS_ROGUE:			ROGUE_Heartbeat( id )
//			case CLASS_PALADIN:			PALADIN_Heartbeat( id )
//			case CLASS_SHAMAN:			SHAMAN_Heartbeat( id )
//			case CLASS_DRUID:			DRUID_Heartbeat( id )
//			case CLASS_WARLOCK:			WARLOCK_Heartbeat( id )
//			case CLASS_HUNTER:			HUNTER_Heartbeat( id )
			case CLASS_PRIEST:			PRIEST_Heartbeat( id )
//			case CLASS_MAGE:			MAGE_Heartbeat( id )
		}
	}
}




//-------------------------------------------
// WC3_Log
//
// Function will simply log to a file as well as amxx log
//-------------------------------------------
WC3_Log( bool:bAmxx, const fmt[], ... )
{
	static szFormattedText[512]
	vformat( szFormattedText, 511, fmt, 3 )

	// Write to amxx log file
	if ( bAmxx )
		log_amx( szFormattedText )

	static szLogFile[128]
	get_configsdir( szLogFile, 127 )
	formatex( szLogFile, 127, "%s/wcg/wcg_error.log", szLogFile )

	// Write to the war3ft log file as well
	log_to_file( szLogFile, szFormattedText )
}


//-------------------------------------------
// WC3_LogError
//
//-------------------------------------------
stock WC3_LogError( szErrorString[], ErrorValue, DebugCode )
{
	WC3_Log( false, "[ERROR] %s: %d [%d]", szErrorString, ErrorValue, DebugCode )
	log_error( AMX_ERR_NATIVE, "[ERROR] %s: %d [%d]", szErrorString, ErrorValue, DebugCode )
}



//-------------------------------------------
// debug_print
//
//-------------------------------------------
stock debug_print( const fmt[], ... )
{
	static szFormattedText[512]
	
	vformat( szFormattedText, 511, fmt, 2 )
	client_print( 0, print_chat, "[DEBUG] %s", szFormattedText )
}